/*
Copyright 2009 - 2013 Peter Krajnc, SCREENWORKER

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
package org.compactmvc.services.factoryservice
{
	import org.compactmvc.services.factoryservice.events.FactoryServiceEvent;
	import org.compactmvc.services.factoryservice.resourcetypes.ResourceByteArray;
	import org.compactmvc.services.factoryservice.resourcetypes.ResourceFrameTwo;
	import org.compactmvc.services.interfaces.IFactoryService;
	import org.compactmvc.services.interfaces.IResourceType;
	import org.compactmvc.interfaces.IModel;
	import org.compactmvc.patterns.Controller;
	import org.compactmvc.patterns.Model;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Timer;

	public class FactoryService extends Model implements IModel, IFactoryService
	{
		private static const TIMER_DELAY:int = 20;
		private var _name:String;
		private var _resources:Array;
		private var _progress:Number = 0;
		private var _stepPosition:int = -1;
		private var _timer:Timer;
		private var _currentResource:IResourceType;
		
		/**
		 * 
		 * @param name
		 * @param controller
		 * 
		 */		
		public function FactoryService(name:String, controller:Controller)
		{
			super(name, controller);
			
			_resources = new Array();
			_timer = new Timer(TIMER_DELAY);
			_timer.addEventListener(TimerEvent.TIMER, timerEventHandler, false, 0, true);
		}
				
		public function errorHandler(message:String):void
		{
			//overriden by subclass
		}
		
		public function completeHandler():void
		{
			//overriden by subclass
		}
		
		public function stepCompleteHandler(position:int, resourceName:String):void
		{
			//overriden by subclass
		}
		
		public function progressHandler(position:int, progress:Number, resourceName:String):void
		{
			//overriden by subclass
		}
		
		protected function reset():void
		{
			_timer.reset();
			_timer = null;
			for each(var resource:IResourceType in _resources)
			{
				resource.reset();
				resource = null;
			}
			_resources = null;
		}
		/**
		 * loads an embeded file like swf and returns progress, complete and error informations:
		 * @usage
		 *  [Embed(source="../assets/preloader.swf", mimeType='application/octet-stream')]
		 *	private const preloaderCls:Class;  
		 * 
		 * @param name
		 * @param byteArray
		 * @return 
		 * 
		 */		
		public function addByteArray(name:String, byteArray:Class):ResourceByteArray
		{
			
			//allowLoadBytesCodeExecution activation in air
			var loaderContext:* = new LoaderContext();
			if(loaderContext.hasOwnProperty("allowLoadBytesCodeExecution"))
			{
				loaderContext.allowLoadBytesCodeExecution = true;
			}	
			var resource:ResourceByteArray = new ResourceByteArray(name, new byteArray() as ByteArray, loaderContext);
			_resources.push( resource );
			return resource;
		}
		
		/**
		 * returns progress, complete and Error informations for the given classPath
		 * @param name //reference name
		 * @param target //target movieclip with min. two frames
		 * @param className //example: com.myproject.ApplicationMain
		 * @return 
		 * 
		 */		
		public function addFrameTwo(name:String, target:MovieClip, classPath:String):ResourceFrameTwo
		{
			var resource:ResourceFrameTwo = new ResourceFrameTwo(name, target, classPath);
			_resources.push( resource );
			return resource;
		}
		
		protected function addRemote(position:int, resource:IResourceType):void
		{
			//...
		}
		
		public function getResource(name:String):IResourceType
		{
			
			for each(var resource:IResourceType in _resources)
			{
				if(resource.resourceName == name)
					return resource;
			}
			throw new Error('Resource with name: ' + name + ' not found!');
			return null;
		}
		
		private function getResourceByPosition(position:uint):IResourceType
		{		
			if(_resources[position] == null)
				throw new Error('Resource at position: ' + position + ' not found!');
				
			return _resources[position];
		}
		
		public function start():void
		{
			if(totalSteps == 0)
				throw new Error("no resources added!");
			if(_currentResource != null)
				throw new Error("Service has already startet!");
			nextStep();
		}
		
		public function nextStep():Boolean
		{
			if(_stepPosition == totalSteps && _resources.length == 0)
				return false;

			//break if current step has not finished!
			if(_currentResource != null)
			{	
				if(!_currentResource.isComplete)
					return false;
			}
			
			_stepPosition ++;
			
			_currentResource = null;
			_currentResource = _resources[_stepPosition];
			EventDispatcher(_currentResource).addEventListener(FactoryServiceEvent.COMPLETE, resourceCompleteHandler, false, 0, true);
			_currentResource.startLoading();
			
			if(!_timer.running)
				_timer.start();
			
			return true;
		}
		
		private function timerEventHandler(e:TimerEvent):void
		{
			if(_currentResource == null)
				return;
			
			var globalProgress:Number = 0;
			var globalComplete:uint = 0;
			var proggressResources:Array = _resources.slice(0, _stepPosition);
			var event:FactoryServiceEvent = new FactoryServiceEvent(FactoryServiceEvent.PROGRESS);
			for each(var item:IResourceType in proggressResources)
			{
				if(item != null)
				{	
					event.resourceName = item.resourceName;
					event.progress = item.progress;
					item.dispatchEvent(event);
					globalProgress += item.progress;
					globalComplete += item.isComplete ? 1 : 0;
				}
				
			}		
			_progress = globalProgress/totalSteps;
			
			//dispatch progress event
			progressHandler(_stepPosition, _progress, _currentResource.resourceName);
				
			if(stepPosition == totalSteps - 1 && _currentResource.isComplete)
			{		
				_progress = 1
				_timer.stop();
				completeHandler();
			}
		}	
		
		private function resourceCompleteHandler(e:FactoryServiceEvent):void
		{
			if(e.resourceName != _currentResource.resourceName)
				return;
			
			//dispatch stepComplete
			stepCompleteHandler(_stepPosition, _currentResource.resourceName);
		}
		
		public function get stepPosition():Number
		{
			return _stepPosition;
		}
		
		public function get totalSteps():Number
		{
			return _resources.length;
		}	
		
		public function get name():String
		{
			return _name;
		}

		public function get progress():Number
		{
			return _progress;
		}

		
	}
}
